/*
 * BaseNetGraph.java
 *
 * Created on September 9, 2007, 2:00 AM
 *
 * Copyright 2008 David D. Emory
 * 
 * This file is part of Five Points. See <http://www.fpdev.org> for
 * additional information regarding the project.
 * 
 * Five Points is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Five Points is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Five Points.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.fpdev.core.basenet;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.fpdev.core.Scenario;
import org.fpdev.core.algo.DepthLimitedSearch;
import org.fpdev.core.transit.TransitPath;
import org.jgrapht.graph.Pseudograph;

/**
 * The graph implementation that serves as the underlying structure for the
 * BaseNetwork; extends a JGraphT Psuedograph. Includes fields specific to the
 * <i>current</i> graph analysis operation (e..g the bike routing factors); 
 * therefore, only one analysis operation can be working with an instance of 
 * this class at any given time.
 * 
 * An instance of this class lies at the heart of BaseNetwork class, which adds 
 * additional functionality for more complex network management tasks. 
 * 
 * @see BaseNetwork
 * 
 * @author demory
 */
public class BaseNetGraph extends Pseudograph<BNode, BLink> {

  private BNode lastTraversed_;
  private int weightMode_;
  private TransitPath.Type transitType_;
  private double bikeTypeFactor_,  bikeTopFactor_;
  private boolean isReverse_;

  /** Creates a new instance of BaseNetGraph */
  public BaseNetGraph() {
    super(BLink.class);
    isReverse_ = false;
  }

  public BaseNetGraph(Iterator<BLink> links) {
    super(BLink.class);
    while (links.hasNext()) {
      BLink link = links.next();
      this.addVertex(link.getFNode());
      this.addVertex(link.getTNode());
      this.addEdge(link.getFNode(), link.getTNode(), link);
    }
  }
  public static final int WEIGHT_LINKLEN = 1;
  public static final int WEIGHT_WALK = 2;
  public static final int WEIGHT_BIKE = 3;
  public static final int WEIGHT_DISTANCE_TRANSIT = 4;
  private Scenario curScen_;

  public void setWeightMode(int mode, Scenario scen) {
    weightMode_ = mode;
    curScen_ = scen;
    isReverse_ = false; // default value
  }

  public void setTransitType(TransitPath.Type type) {
    transitType_ = type;
  }

  public void setBikeFactors(double bikeTypeFactor, double bikeTopFactor) {
    bikeTypeFactor_ = bikeTypeFactor;
    bikeTopFactor_ = bikeTopFactor;
  }

  public void setIsReverse(boolean r) {
    isReverse_ = r;
  }

  public boolean getIsReverse() {
    return isReverse_;
  }

  @Override
  public double getEdgeWeight(BLink link) {
    return getEdgeWeight(link, lastTraversed_);
  }

  public double getEdgeWeight(BLink link, BNode inNode) {
    if (link.getID() < 0 || (curScen_ != link.getScenario() && !link.getScenario().isAncestorOf(curScen_))) {
      return 100000000;
    }
    switch (weightMode_) {
      case WEIGHT_LINKLEN:
        return link.getLengthFeetDbl();
      case WEIGHT_WALK:
        return walkWeight(link);
      case WEIGHT_BIKE:
        return bikeWeight(link, inNode);
      /*case WEIGHT_DISTANCE_TRANSIT:
        return (link.supportsTransitType(transitType_) && link.getID() > 0) ? link.getLengthFeetDbl() : 100000000;*/
    }
    return 0;
  }

  private double walkWeight(BLink e) {
    if (!e.isWalkable()) {
      return 100000000;
    }
    return e.getLengthFeet();
  }

  public void setLastTraversed(BNode node) {
    lastTraversed_ = node;
  }

  private double bikeWeight(BLink e, BNode inNode) {
    boolean dbg = e.getID() == 1000474; //false; //(e.getID()==83841 || e.getID()==83842 || e.getID()==83835 || e.getID()==83775);
    if (dbg) {
      System.out.println("bW link " + e.getID() + " in=" + inNode.getID() + " r=" + isReverse_);
    }
    if (isReverse_) {
      inNode = opposite(inNode, e);
    }
    if (!e.isBikeable(inNode)) {
      if (dbg) {
        System.out.println(" not bikeable");
      }
      return 100000000;
    }

    double typeValue = 0, topValue = e.getUpSlope(inNode) / 5.0;

    if (e.getClassType() == BLink.CLASS_STREET) {
      typeValue = ((BLinkStreet) e).getBikeFacTypeValue();
    }

    if (dbg) {
      System.out.println(" typeVal=" + typeValue + " topVal=" + topValue);
    }

    double len = e.getLengthFeetDbl();
    double w = len * (1 - bikeTypeFactor_ - bikeTopFactor_) + (len * bikeTypeFactor_ * typeValue) + (len * bikeTopFactor_ * topValue);
    if (w < 0) {
      w = 0;
    }
    if (dbg) {
      System.out.println(" weight= " + w);
    }
    return w;

  }

  public BNode opposite(BNode node, BLink link) {
    if (this.getEdgeSource(link) == node) {
      return getEdgeTarget(link);
    }
    if (this.getEdgeTarget(link) == node) {
      return getEdgeSource(link);
    }
    return null;
  }

  public boolean isSimplePath() {
    Iterator<BNode> nodes = vertexSet().iterator();
    int endPoints = 0;
    while (nodes.hasNext()) {
      int degree = degreeOf(nodes.next());
      if (degree >= 3) {
        return false;
      }
      if (degree == 1) {
        endPoints++;
      }
    }
    return (endPoints == 2);
  }

  public void convertSimpleGraphToPath(Path path, BNode start) {
    if (!isSimplePath()) {
      return;
    } // throw exception

    Iterator<BLink> links = this.edgesOf(start).iterator();
    while (links.hasNext()) {
      BLink link = links.next();
      if (this.containsEdge(link)) {
        path.addDirectedFirstLink(link, start);
        addToSimplePath(path, opposite(start, link), link);
        break;
      }
    }
    System.out.println("convertSimpleGraphToPath len=" + path.linkCount());

  }

  private void addToSimplePath(Path path, BNode node, BLink inLink) {
    Iterator<BLink> links = this.edgesOf(node).iterator();
    while (links.hasNext()) {
      BLink link = links.next();
      if (link != inLink && this.containsEdge(link)) {
        path.addToEnd(link);
        addToSimplePath(path, opposite(node, link), link);
        break;
      }
    }
  }

  public List<BNode> getEndPoints() {
    List<BNode> pts = new LinkedList<BNode>();
    Iterator<BNode> nodes = vertexSet().iterator();
    int endPoints = 0;
    while (nodes.hasNext()) {
      BNode node = nodes.next();
      if (degreeOf(node) == 1) {
        pts.add(node);
      }
    }
    return pts;

  }

  public BaseNetGraph getReachableSubNet(BNode node, int limit) {//, boolean reverse) {
    double oldType = bikeTypeFactor_, oldTop = bikeTopFactor_;
    setBikeFactors(0, 0);


    DepthLimitedSearch traverser = new DepthLimitedSearch(this, DepthLimitedSearch.TYPE_BIKE, limit);
    traverser.execute(node);
    BaseNetGraph subGraph = new BaseNetGraph(traverser.visitedLinks());

    //System.out.println("check node "+node.getID()+" "+subGraph.containsVertex(node));
    bikeTypeFactor_ = oldType;
    bikeTopFactor_ = oldTop;
    //isReverse_ = oldIsReverse;

    subGraph.isReverse_ = isReverse_;
    //System.out.println("sub isRev="+subGraph.isReverse_);
    subGraph.bikeTypeFactor_ = bikeTypeFactor_;
    subGraph.bikeTopFactor_ = bikeTopFactor_;
    subGraph.weightMode_ = weightMode_;
    return subGraph;
  }
}
